home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-11-18 | 54.8 KB | 1,536 lines | [TEXT/MPS ] |
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- #
- # Apple Macintosh Developer Technical Support
- #
- # SoundUnit
- #
- # SoundUnit.p - MPW 3.0/3.1 Pascal Source
- #
- # Versions:
- # 1.03 May, 1990
- # 1.1b1 Nov, 1990 MPW 3.2 update
- #
- # Components:
- # SoundApp.make May 1, 1990 MPW build script
- # SoundApp.p May 1, 1990 Pascal source code
- # SoundApp.r May 1, 1990 Rez source code
- # SoundAppSnds.r May 1, 1990 Rez source code
- # SoundUnit.p May 1, 1990 Pascal source code
- #
- # Version comments
- #
- # 1.1: This is the "new" SoundUnit which adds some new features.
- # • Some knowledge of the new Sound Manager is present
- # in areas that were work-arounds for old Sound Manager bugs
- # • Conversion to MPW 3.2 was established (with some amount of pain)
- # • Added the new Sound Manager error strings
- # • Checking of the sound header for supported encode values
- # • The amp value is ignored (never used) in a freqDurationCmd
- # • Added functions to test sound hardware/software features
- # such as stereo, MACE, Sound Input
- #
- #
- # Formatting was done with FONT = Monaco, SIZE = 9, TABS = 3
- #
- #
- # SoundApp.p is a sample application source file for demonstrating the
- # Sound Manager. This portion of the source code handles the Sound Manager
- # part of the application. This UNIT can be used by others.
- #
- # Jim Reekes E.O., Macintosh Developer Technical Support
- # Tuesday, January 30, 1990 1:01 PM
- #
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- UNIT SoundUnit;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- INTERFACE
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- USES
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {Only include the interface files that I really need. This helps MPW
- to compile faster.}
-
- Types, Traps, Memory, Resources, OSUtils, Errors, FixMath, FP, Sound,
- SoundInput, GestaltEqu;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- CONST
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- {refer to the SoundAppSnds.r file for documentation on these values}
- kOctave1 = 0; {octaves of MIDI values}
- kOctave2 = 12;
- kOctave3 = 24;
- kOctave4 = 36;
- kOctave5 = 48;
- kOctave6 = 60;
- kOctave7 = 72;
- kOctave8 = 84;
- kOctave9 = 96;
- kOctave10 = 108;
- kOctave11 = 120;
-
- Akey = -3; {the key A}
- Bbkey = -2; {the key B flat}
- Bkey = -1; {the key B}
- Ckey = 0; {the key C}
- Dbkey = 1; {the key D flat}
- Dkey = 2; {the key D}
- Ebkey = 3; {the key D flat}
- Ekey = 4; {the key E}
- Fkey = 5; {the key F}
- Gbkey = 6; {the key G flat}
- Gkey = 7; {the key G}
- Abkey = 8; {the key A flat}
-
- {These are other constants used in the SoundUnit}
- kInitNone = 0; {no init options}
- kWait = FALSE; {wait for the channel}
- kSMAsynch = TRUE; {asynchronous Sound Manager call}
- kWaveSize = 512; {standard size of wave table}
- kSyncID = $12345678; {identifier used in syncCmd}
- kOneSecond = 2000; {one second frequency duration}
-
- {These are used as flags in the sound channel to determine the state
- of that channel. The 'snth' IDs are used when the channel is in use
- to determine what that channel in intended for.}
- kNoSynth = 0; {no synth is specified}
- kChanComplete = -1; {channel has completed}
- kChanFree = MAXLONGINT; {channel is not in use}
- kSoundComplete = $1234; {flag for callBackCmd}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- TYPE
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- SndCmdPtr = ^SndCommand; {Ptr to a sound command, for type coersion}
- IntPtr = ^INTEGER; {Ptr to a INTEGER, for type coersion}
-
- {I have declared a few TYPEs below to help examine 'snd ' resources.
- I have to break them up into individual pieces because they are
- variable sized records.}
-
- Snd1Header = RECORD
- format: INTEGER;
- numSynths: INTEGER;
- END;
- Snd1HdrPtr = ^Snd1Header;
- Snd1HdrHndl = ^Snd1HdrPtr;
-
- SynthInfo = RECORD
- synthID: INTEGER;
- initOption: LONGINT;
- END;
- SynthInfoPtr = ^SynthInfo;
-
- Snd2Header = RECORD
- format: INTEGER;
- refCount: INTEGER;
- END;
- Snd2HdrPtr = ^Snd2Header;
- Snd2HdrHndl = ^Snd2HdrPtr;
-
- {I have created my own sound channel type. It extends the normal
- sound channel by adding a few fields. To confirm that the channel
- in question is mine, I set the userInfo field to something that I
- can recognize. I keep the 'snd ' resource handle associated to this
- channel too. This allows me to dispose of the data once the channel
- has completed its duties.}
-
- MyChanType = RECORD {this is a 1064 byte structure}
- theChan: SndChannel; {must keep sound channel as first field}
- dataHandle: Handle;
- END;
- MyChanPtr = ^MyChanType;
-
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- VAR {The “g” prefix is used to emphasize that a variable is global.}
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- {These four global pointers are to my sound channels. One of them has to
- be global and I could utilize the fact that they are a linked list. That
- would complicate things to some degree, and it really wouldn’t have that
- much of an advantage.}
-
- gChan1: MyChanPtr;
- gChan2: MyChanPtr;
- gChan3: MyChanPtr;
- gChan4: MyChanPtr;
-
- {This is the global flag that is set once the sound channel’s call back
- procedure has been called. This, in my case, means the channel has
- completed its duties and is time for disposing.}
-
- gCalledBack: BOOLEAN;
-
- {gChanOpen is a flag set to determine if the application has a sound
- channel open. It’s really not feasible to determine if a sound is being
- made at any given point.}
-
- gChanOpen: BOOLEAN;
-
- {gNewSndMgr is a flag used to determine if the application is running
- with the new Sound Manager. This was initially shipped in System 6.0.6.
- This flag is setup in the InitSoundUnit routine and used by the rest of this
- UNIT. This is new for version 1.1}
-
- gNewSndMgr: BOOLEAN;
-
- {gHasMACE is a flag used to determine if the MACE compression/decompression
- routines are available. It is necessary for MACE to be present before using
- a compressed sound. This is new for version 1.1}
-
- gHasMACE: BOOLEAN;
-
- {gHasSndInput is a flag used to determine if the Sound Input Manager is
- available. This is new for version 1.1}
-
- gHasSndInput: BOOLEAN;
-
- {gHasStereo is a flag used to determine if stereo hardware is present.
- This is new for version 1.1}
-
- gHasStereo: BOOLEAN;
-
-
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- {The routines below are for public consumption in this UNIT. Note that
- these all return standard sound channels to allow easy of modifications.
- I can change the structure of my own sound channels and the code without
- forcing a change in the application that uses this UNIT.
-
- VERSION 1.1: No longer putting the SANELib into a seperate unit, which then
- needs to be unloaded. Instead, I merge it into the Main segment. Refer to
- the Make file for more information.}
-
- PROCEDURE _SoundUnit;
-
- FUNCTION InitSoundUnit: OSErr;
- FUNCTION HasNewSndMgr: BOOLEAN;
- FUNCTION HasMACE: BOOLEAN;
- FUNCTION HasSoundInput: BOOLEAN;
- FUNCTION HasStereo: BOOLEAN;
- FUNCTION SoundCompletion: BOOLEAN;
- FUNCTION SndChanOpen: BOOLEAN;
- FUNCTION SetSquareTimbre(squareChan: SndChannelPtr; timbre: INTEGER;
- immediate: BOOLEAN): OSErr;
- FUNCTION SendFreqDur(chan: SndChannelPtr; duration: INTEGER; freq: LONGINT): OSErr;
- FUNCTION SendQuiet(chan: SndChannelPtr; immediate: BOOLEAN): OSErr;
- FUNCTION SendRest(chan: SndChannelPtr; duration: INTEGER): OSErr;
- PROCEDURE DoSoundComplete;
- PROCEDURE FreeAllChans;
- PROCEDURE FreeSoundUnit;
- FUNCTION SoundComplete(chan: SndChannelPtr): OSErr;
- FUNCTION HoldSnd(sndHandle: Handle): OSErr;
- FUNCTION GetSynthInfo(sndHandle: Handle): SynthInfo;
- FUNCTION GetSndDataOffset(sndHandle: Handle;
- VAR dataType, waveLength: INTEGER): LONGINT;
- FUNCTION GetSampleChan(VAR sampleChan: SndChannelPtr; init: LONGINT;
- sndInstrument: Handle): OSErr;
- FUNCTION InstallWave(waveChan: SndChannelPtr; aWavePtr: Ptr;
- waveLength: INTEGER): OSErr;
- FUNCTION GetWaveChans(VAR waveChan1, waveChan2,
- waveChan3, waveChan4: SndChannelPtr): OSErr;
- FUNCTION GetSquareChan(VAR squareChan: SndChannelPtr; timbre: INTEGER): OSErr;
- FUNCTION PlaySong(chan: SndChannelPtr; sndSong: Handle): OSErr;
- FUNCTION ReleaseSynch(chan: SndChannelPtr): OSErr;
- FUNCTION SynchChans(chan1, chan2, chan3, chan4: SndChannelPtr): OSErr;
- FUNCTION Play4Waves(waveChan1, waveChan2, waveChan3, waveChan4: SndChannelPtr;
- song1, song2, song3, song4: Handle): OSErr;
- FUNCTION HyperSndPlay(sndHandle: Handle): OSErr;
- FUNCTION AsynchSndPlay(sndHandle: Handle): OSErr;
-
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- IMPLEMENTATION
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- PROCEDURE _SoundUnit;
-
- {This is a dummy routine to allow the application to unload this UNIT.}
-
- BEGIN
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Initialization}
- FUNCTION InitSoundUnit: OSErr;
-
- {Create storage for each of the four channels (4 * 1064 bytes) and
- initialize them. If any of the channels cannot be allocated, return an
- error. Also initialized our global flag “gCalledBack.” An interesting
- modification would be to allow the caller to pass in the number of
- channels the application really intends on using. If the user only wants
- one channel, then we could just allocate one instead of four. These
- channels are used at interrupt time.
-
- Version 1.1: Added the new Sound Manager flag, gNewSndMgr. First I have to
- test if the _SoundDispatch trap is available, since SndManagerVersion is
- a selector for _SoundDispatch. This this trap isn't available, then calling
- SndManagerVersion would result in an unimplemented instruction. If an error
- is returned, it is the old Sound Manager. If it is zero, then the call
- is available (via-MIDI Mgr?) but it isn't the new Sound Manager. Greater
- than zero means it is the new Sound Manager. To determine if MACE is
- available, there are two case. If the new Sound Manager is running then
- MACE may be built in. This is easy enough to test for by calling the new
- trap call. Otherwise I have to test for the presence of the MACE
- snth resources. Also new is a flag to determine if Sound Input is available.
- The Gestalt call in System 6.0.7 will only report if the machine has
- Apple built in Sound Input hardware. Other third parties may include an
- external device which will allow recording. To determine if such a
- device is present, I use SPBGetIndexdDevice to find the fist one. If
- this returns noErr then Sound Input is available. Also, the icon handle
- returned has to be disposed of. The MACE snths are only for the old
- Sound Manager which did not have built-in MACE. They could only be present
- if the user ran the MACE Installer Scripts which are available from APDA.}
-
-
- FUNCTION InitMyChan(VAR newChan: MyChanPtr): BOOLEAN;
- BEGIN
- newChan:= MyChanPtr(NewPtrClear(SizeOf(MyChanType)));
- IF newChan <> NIL THEN BEGIN
- newChan^.theChan.qLength:= stdQLength;
- newChan^.theChan.userInfo:= kChanFree;
- InitMyChan:= TRUE;
- END ELSE
- InitMyChan:= FALSE;
- END;
-
- BEGIN
- gChanOpen:= FALSE;
- gCalledBack:= FALSE;
- IF InitMyChan(gChan1) THEN
- IF InitMyChan(gChan2) THEN
- IF InitMyChan(gChan3) THEN
- IF InitMyChan(gChan4) THEN;
- InitSoundUnit:= MemError;
-
- gNewSndMgr:= HasNewSndMgr; {is the "new" Sound Manager running?}
- gHasMACE:= HasMACE; {is MACE (audio compression/expansion) running?}
- gHasSndInput:= HasSoundInput; {is the Sound Input Manager running?}
- gHasStereo:= HasStereo; {is there stereo support on this machine?}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION HasNewSndMgr: BOOLEAN;
-
- VAR
- response: LONGINT;
-
- {VERSION 1.1: This is the external routine for users to determine if
- the new Sound Manager is available.}
-
- BEGIN
- IF noErr = Gestalt(gestaltSoundAttr, response) THEN
- HasNewSndMgr:= BTst(response, gestaltSoundIOMgrPresent)
- ELSE
- HasNewSndMgr:= FALSE;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION HasMACE: BOOLEAN;
-
- VAR
- devName: Str255;
- environRec: SysEnvRec;
- theCmd: SndCommand;
- response: LONGINT;
-
- {VERSION 1.1: This is the external routine for users to determine if
- audio compression/expansion is available.}
-
- BEGIN
- IF HasNewSndMgr THEN
- HasMACE:= MACEVersion.majorRev > 0 {is the built-in MACE here?}
- ELSE BEGIN
- HasMACE:= FALSE ; {default to no MACE}
- theCmd.cmd:= versionCmd; {use versionCmd to check}
- theCmd.param1:= 0;
- theCmd.param2:= 0;
- IF noErr = SndControl(MACE3snthID, theCmd) THEN {if no errors, then...}
- IF noErr = SndControl(MACE6snthID, theCmd) THEN
- HasMACE:= TRUE; {we got MACE}
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION HasSoundInput: BOOLEAN;
-
- VAR
- devName: Str255;
- devIconHandle: Handle;
-
- {VERSION 1.1: This is the external routine for users to determine if
- sound input is available.}
-
- BEGIN
- HasSoundInput:= FALSE;
- IF HasNewSndMgr THEN BEGIN
- IF SPBVersion.majorRev > 0 THEN BEGIN
- IF SPBGetIndexedDevice(1, devName, devIconHandle) = noErr THEN BEGIN
- DisposeHandle(devIconHandle);
- HasSoundInput:= TRUE;
- END;
- END;
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION HasStereo: BOOLEAN;
-
- VAR
- response: LONGINT;
-
- {VERSION 1.1: This is the external routine for users to determine if
- sound input is available.}
-
- BEGIN
- IF noErr = Gestalt(gestaltSoundAttr, response) THEN
- HasStereo:= BTst(response, gestaltStereoCapability)
- ELSE
- HasStereo:= FALSE;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION SoundCompletion: BOOLEAN;
-
- {This routine can be called to determine if the sound has completed. When
- this is true, the sound data can be disposed of. The global “gCalledBack”
- determines whether the sound has completed or not and it is set by the
- sound channel’s completion routine. Soundcompletion is placed in the Main
- segment because it is called by the event loop.}
-
- BEGIN
- SoundCompletion:= gCalledBack;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION SndChanOpen: BOOLEAN;
-
- {This routine can be called at any time. It will return TRUE when the
- SoundUnit has an open channel. This can be can considered the same as
- sound being active. As long as the channel is open, no other channels can
- be opened. It is placed in the Main segment because it is called by the
- event loop.}
-
- BEGIN
- SndChanOpen:= gChanOpen;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SetSquareTimbre(squareChan: SndChannelPtr; timbre: INTEGER;
- immediate: BOOLEAN): OSErr;
-
- {Given a channel and timbre (sounds like “tom burr”), this will adjust the
- tone quality of the square wave synthesizer. Changing the tone can only
- be done before playing a frequency. On a Mac with the Apple Sound Chip, this
- can be done in real time while a sound is playing. But, since there’s no
- supported method for determining if the ASC is available I have to assume
- that it’s not. I use the immediate flag to determine if the user wants to
- change the timbre now, or queue the command. If the queue is full, it
- will wait for the command to be accepted.
-
- BUG NOTE: There is a bug in the Sound Manager running on the Mac Plus or
- SE where sending a timbreCmd with a timbre of 255 (a legal value) will
- crash. The difference between 254 and 255 isn’t audible, so I only allow
- a maximum of 254 in any case.}
-
- VAR
- theCmd: SndCommand;
-
- BEGIN
- IF timbre > 254 THEN
- timbre:= 254;
- WITH theCmd DO BEGIN
- cmd:= timbreCmd;
- param1:= timbre;
- param2:= 0;
- END;
- IF immediate THEN
- SetSquareTimbre:= SndDoImmediate(squareChan, theCmd)
- ELSE
- SetSquareTimbre:= SndDoCommand(squareChan, theCmd, kWait);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SendFreqDur(chan: SndChannelPtr; duration: INTEGER;
- freq: LONGINT): OSErr;
-
- {Given a channel and frequency/duration information, this will place the
- frequency into the channel’s queue. The freq is a three-byte parameter
- with the high byte being ignored. I use SndDoCommand with the noWait
- flag set to wait for the channel to except the command in case the queue
- is currently full.}
-
- VAR
- theCmd: SndCommand;
-
- BEGIN
- WITH theCmd DO BEGIN
- cmd:= freqDurationCmd;
- param1:= duration;
- param2:= freq;
- END;
- SendFreqDur:= SndDoCommand(chan, theCmd, kWait);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SendQuiet(chan: SndChannelPtr; immediate: BOOLEAN): OSErr;
-
- {Given a channel, this will place a quietCmd into the channel’s queue. I
- use SndDoCommand with the noWait flag set to wait for the channel to
- accept the command in the case it is currently full.
-
- BUG NOTE: A sequence of frequencies and rests will not work unless quietCmds
- are between them. Rests have to be made quiet before they rest, if that
- makes any more sense. A freqDurationCmd will loop, causing the sound in
- progress to continue, until a quietCmd is received.}
-
- VAR
- theCmd: SndCommand;
-
- BEGIN
- WITH theCmd DO BEGIN
- cmd:= quietCmd;
- param1:= 0;
- param2:= 0;
- END;
- IF immediate THEN
- SendQuiet:= SndDoImmediate(chan, theCmd)
- ELSE
- SendQuiet:= SndDoCommand(chan, theCmd, kWait);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SendRest(chan: SndChannelPtr; duration: INTEGER): OSErr;
-
- {Given a channel and duration, this will place the rest into the channel’s
- queue. Before sending a rest a quietCmd is needed. Rests don’t work
- unless you tell the Sound Manager to be quiet too. I use SndDoCommand
- with the noWait flag set to wait for the channel to accept the command in
- case it is currently full.}
-
- VAR
- theCmd: SndCommand;
- theErr: OSErr;
-
- BEGIN
- theErr:= SendQuiet(chan, kWait);
- IF theErr = noErr THEN BEGIN
- WITH theCmd DO BEGIN
- cmd:= restCmd;
- param1:= duration;
- param2:= 0;
- END;
- theErr:= SndDoCommand(chan, theCmd, kWait);
- END;
- SendRest:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- PROCEDURE FreeChan(myChan: MyChanPtr);
-
- {Test if the channel is free, and if not then call SndDisposeChannel.
- This will release the synthesizer (snth resource) code and the
- required hardware. If we didn’t do this, no other channels would
- work. I also test myChan for having a snd resource attached to the
- channel. If so, then I mark it as purgeable and reset the data to NIL.
-
- BUG NOTE: Calling SndDisposeChannel while or immediately after playing
- a sequence of frequencies would often hang/crash a non-Apple Sound Chip based Mac.
- Issuing a quietCmd first kept the Sound Manager happy and my Mac from
- crashing.}
-
- VAR
- theErr: OSErr;
-
- BEGIN
- IF myChan^.theChan.userInfo <> kChanFree THEN BEGIN
- theErr:= SendQuiet(SndChannelPtr(myChan), NOT kWait); {ignore error}
- theErr:= SndDisposeChannel(SndChannelPtr(myChan), NOT kWait);
- myChan^.theChan.userInfo:= kChanFree;
- END;
- IF myChan^.dataHandle <> NIL THEN BEGIN
- HUnlock(myChan^.dataHandle);
- HPurge(myChan^.dataHandle);
- myChan^.dataHandle:= NIL;
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- PROCEDURE DoSoundComplete;
-
- {This routine is called by an application that established a sound to be
- played asynchronously. This is, in effect, the routine to be used after
- the completion routine has been called. The application should call this
- once SoundCompletion returns TRUE. In the case the application is using
- multiple channels, we will only free a channel once it has been marked as
- kChanComplete. I do not reset the gCalledBack until all the open channels
- are freed.}
-
- BEGIN
- IF gChan1^.theChan.userInfo = kChanComplete THEN
- FreeChan(gChan1);
- IF gChan2^.theChan.userInfo = kChanComplete THEN
- FreeChan(gChan2);
- IF gChan3^.theChan.userInfo = kChanComplete THEN
- FreeChan(gChan3);
- IF gChan4^.theChan.userInfo = kChanComplete THEN
- FreeChan(gChan4);
-
- IF (gChan1^.theChan.userInfo = kChanFree)
- & (gChan2^.theChan.userInfo = kChanFree)
- & (gChan3^.theChan.userInfo = kChanFree)
- & (gChan4^.theChan.userInfo = kChanFree) THEN BEGIN
- gCalledBack:= FALSE;
- gChanOpen:= FALSE; {no longer making noises}
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- PROCEDURE FreeAllChans;
-
- {This is the routine that will force all channels to be released. It also
- resets the gCalledBack flag. This is used by all routines just before
- opening a new channel to force all channels to be disposed.}
-
- BEGIN
- FreeChan(gChan1);
- FreeChan(gChan2);
- FreeChan(gChan3);
- FreeChan(gChan4);
- gCalledBack:= FALSE;
- gChanOpen:= FALSE; {no longer making noises}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- PROCEDURE FreeSoundUnit;
-
- {This is the final routine to be called by the application when it is has
- finished using this unit. This will dispose of all the channels and
- memory used by this unit.}
-
- BEGIN
- FreeAllChans;
- IF gChan1 <> NIL THEN
- DisposePtr(Ptr(gChan1));
- IF gChan2 <> NIL THEN
- DisposePtr(Ptr(gChan2));
- IF gChan3 <> NIL THEN
- DisposePtr(Ptr(gChan3));
- IF gChan4 <> NIL THEN
- DisposePtr(Ptr(gChan4));
- gCalledBack:= FALSE;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoCallBack(chan: SndChannelPtr; theCmd: SndCommand);
-
- {This will be called at interrupt time by the Sound Manager when it
- receives a callBackCmd. I use the second parameter of the command to hold
- my application’s A5 reference. I first set up A5 so that I can access my
- globals. I mark the given channel as being complete and set gCalledBack
- to TRUE. This lets the application know that the callBackCmd has been
- processed. The callBackCmd can be used for other purposes, and the first
- parameter of the command could be a flag to a more extensive routine.
- Synchronizing the application with the channel is possible with this
- method.
-
- WARNING: This routine MUST be resident in memory and cannot make a call
- to a non-resident segment. I put this into the Main segment because of
- this.
-
- BUG NOTE: System 6.0.4 has a bug in _SndPlay when using a sampled sound
- 'snd '. A bogus callBackCmd is placed into the queue immediately after
- the bufferCmd used to play the sound. This bogus callBackCmd will cause
- my callBackProc to be called when I wasn’t expecting it. I have been
- using the command’s second parameter to contain my A5 address. If I’m
- given a bogus callBackCmd, it would be really bad to set A5 address to
- this bogus parameter in the command. I found that the bogus callBackCmd
- contains the handle to the 'snd ' passed in to _SndPlay. I also found
- that param1 contains the handle’s state bits (results of HGetState). To
- work with this bug I set my real callBackCmd’s param1 to a specific value
- when I installed it into the queue. See the SoundComplete routine. Then
- I test the callBackCmd to make sure I’m dealing with the real one.}
-
- VAR
- theA5: LONGINT;
-
- BEGIN
- IF thecmd.param1 = kSoundComplete THEN BEGIN {if it’s my callBackCmd}
- theA5:= SetA5(theCmd.param2); {refer to tech note 208}
- chan^.userInfo:= kChanComplete; {this channel is done}
- gCalledBack:= TRUE;
- theA5:= SetA5(theA5); {restore original A5}
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SoundComplete(chan: SndChannelPtr): OSErr;
-
- {I use this to install the callBackCmd into the given channel. I need to
- pass in our A5 along with the command so that the callBack routine can
- access my globals. I also wait until the channel is ready for another
- command in the case of the channel being full. Once the Sound Manager
- calls my call back procedure I will dispose of the channel. So, this is
- the last sound command to be sent to a channel. I pass to the call back
- A5 in the second parameter of the callBackCmd. Refer to Tech Note #208.}
-
- VAR
- theCmd: SndCommand;
-
- BEGIN
- WITH theCmd DO BEGIN
- cmd:= callBackCmd;
- param1:= kSoundComplete;
- param2:= SetCurrentA5;
- END;
- SoundComplete:= SndDoCommand(chan, theCmd, kWait);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION ChanAvailable(theChan: SndChannelPtr): OSErr;
-
- {This routine will test the given channel to see if it will really produce
- sound. The Sound Manager in System 6.0x will return noErr even if the
- channel isn’t going to work. In the future the Sound Manager will return
- the proper error. Until then, I use this routine to determine this for me.
- There can only be a single channel at any time, unless I have the wave
- table synthesizer open. This will allow four channels. Channels have
- a pointer to the next channel, and if this is not NIL I suspect the
- given channel will not work. I test the given channel for being a
- wave type, and if so I need to see if the other channels I’ve got are
- also wave type. If it doesn’t look like the channels is available, I
- return badChannel. It is important to set the userInfo field of a channel
- before calling this routine!
-
- This routine assumes theChan passed in is the first channel allocated.
- Channels are held in a linked list, and the first one to be tested has
- to be the first one allocated.
-
- BUG NOTE: If an application is not using the Sound Manager and instead
- uses the older Sound Driver, any given channel will fail. Or if the other
- application does not release is channels, then my channels will not work.
- The most noticeable offender of this is HyperCard. Friendly applications
- will dispose of their channels at suspend/resume times or ASAP.
-
- Version 1.1: Added the new Sound Manager test. The new Sound Manager will
- allow multiple sound channels, and returns proper error codes.}
-
- FUNCTION IsMyChan(chan: SndChannelPtr): BOOLEAN;
- BEGIN
- IsMyChan:= ((chan = SndChannelPtr(gChan1)) {is it one of ours?}
- | (chan = SndChannelPtr(gChan2))
- | (chan = SndChannelPtr(gChan3))
- | (chan = SndChannelPtr(gChan4)));
- END;
-
- FUNCTION CompatibleChan(waveChan: SndChannelPtr): BOOLEAN;
- BEGIN
- CompatibleChan:= (waveChan^.userInfo = waveTableSynth) {wave or..}
- | (waveChan^.userInfo = kChanFree); {free chan}
- END;
-
- BEGIN
- ChanAvailable:= noErr;
- IF NOT gNewSndMgr THEN BEGIN
- IF theChan^.nextChan <> NIL THEN BEGIN {looks bad}
- ChanAvailable:= badChannel; {prepare to fail}
- IF theChan^.userInfo = waveTableSynth THEN BEGIN {last attempt}
- IF IsMyChan(SndChannelPtr(theChan^.nextChan))
- & CompatibleChan(SndChannelPtr(gChan1))
- & CompatibleChan(SndChannelPtr(gChan2))
- & CompatibleChan(SndChannelPtr(gChan3))
- & CompatibleChan(SndChannelPtr(gChan4))
- THEN
- ChanAvailable:= noErr; {got lucky}
- END;
- END;
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SndDataAvailable(sndHandle: Handle): OSErr;
-
- {Given a resource handle, this will attempt to load it into memory. If the
- data is not available, then return an error.}
-
- BEGIN
- SndDataAvailable:= noErr;
- IF sndHandle <> NIL THEN BEGIN
- LoadResource(sndHandle);
- IF sndHandle^ = NIL THEN
- SndDataAvailable:= nilHandleErr; {master pointer is NIL}
- END ELSE
- SndDataAvailable:= nilHandleErr; {user passed a NIL handle}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION HoldSnd(sndHandle: Handle): OSErr;
-
- {This is used to put the given sound resource into memory and hold it there.
- I also use a MoveHHi to keep the heap from being fragmented. If this
- fails, then I return an error. I dereference the handle and check if the
- master pointer is NIL. This would mean the data could not be loaded.}
-
- BEGIN
- IF sndHandle <> NIL THEN BEGIN
- LoadResource(sndHandle);
- IF sndHandle^ = NIL THEN
- HoldSnd:= nilHandleErr {master pointer is NIL}
- ELSE BEGIN
- HoldSnd:= noErr;
- MoveHHi(sndHandle);
- HLock(sndHandle);
- END;
- END ELSE
- HoldSnd:= nilHandleErr; {user passed a NIL handle}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION GetSynthInfo(sndHandle: Handle): SynthInfo;
-
- {This routine will return the 'snth' resource ID specified by the sound.
- I use this to determine if the given sound will work with _SndPlay.
- This routine does not require the data to be in memory when called. It
- also doesn’t lock it down while looking for the information. I will
- mark the resource as being purgeable in the case the resource attributes
- does not have it’s purgeable bit set.
-
- VERSION 1.1: If no synth information is found in the snd then by default
- it is assumed to be for the squareWaveSynth.}
-
- VAR
- soundPtr: Ptr;
- theErr: OSErr;
-
- BEGIN
- GetSynthInfo.synthID:= kNoSynth;
- GetSynthInfo.initOption:= 0;
- theErr:= SndDataAvailable(sndHandle);
- IF theErr = noErr THEN BEGIN
- soundPtr:= sndHandle^;
- IF Snd1HdrPtr(soundPtr)^.format = firstSoundFormat THEN BEGIN
- IF Snd1HdrPtr(soundPtr)^.numSynths <> kNoSynth THEN BEGIN
- soundPtr:= Ptr(ORD4(soundPtr) + SizeOf(Snd1Header));
- GetSynthInfo.synthID:= SynthInfoPtr(soundPtr)^.synthID;
- GetSynthInfo.initOption:= SynthInfoPtr(soundPtr)^.initOption;
- END ELSE
- GetSynthInfo.synthID:= squareWaveSynth;
- END ELSE BEGIN {snd is a format 2 for HyperCard}
- GetSynthInfo.synthID:= sampledSynth;
- GetSynthInfo.initOption:= 0; {no options currently supported}
- END;
- HPurge(sndHandle);
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION GetSndDataOffset(sndHandle: Handle;
- VAR dataType, waveLength: INTEGER): LONGINT;
-
- {This routine will cruise through the given snd resource. It will locate
- the sound data, if any, and return its type and offset into the resource.
- I prefer to return an offset instead of a pointer because I don’t want
- to have the data locked in memory. If I return an offset, the caller
- can decide when and if it wants the resource locked down to access the
- sound data. The first step in finding this data is to determine if I’m
- looking at a format 1 or 2 type snd. A type 2 is easy, but a type 1 will
- require me to find the number of snths specified and then to skip over
- each one including the init option. Once this is done, I have a pointer
- to the number of commands in the snd. When I’ve found the first one, I
- examine it to find out if it is a sound data command. Being it’s a sound
- resource, the command will also have its dataOffsetFlag set. Once I’ve
- found a command I’m looking for I return its type and offset, then get out
- of the REPEAT block. OTHERWISE I go on to the next command. All of this
- makes it possible to get the sound data for use as an instrument sound.
- Typically this will be a sampled sound.
-
- dataType is the type of data contained in the snd handle. This would
- be either sampled sound, a wave table, or possibly no snth is specified.
- The later could be used for any of the snth channels.
-
- waveLength is set to the length of the wave table data, if any is present.
-
- WARNING: Do not send this routine a NIL handle.}
-
- VAR
- synths,
- howManyCmds: INTEGER;
- cruisePtr: Ptr;
-
- BEGIN
- GetSndDataOffset:= 0;
- dataType:= kNoSynth;
- waveLength:= 0;
- cruisePtr:= sndHandle^;
- IF cruisePtr <> NIL THEN BEGIN
- IF Snd1HdrPtr(cruisePtr)^.format = firstSoundFormat THEN BEGIN
- synths:= Snd1HdrPtr(cruisePtr)^.numSynths;
- cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(Snd1Header));
- cruisePtr:= Ptr(ORD4(cruisePtr) + (SizeOf(SynthInfo) * synths));
- END ELSE
- cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(Snd2Header));
- howManyCmds:= IntPtr(cruisePtr)^; {pointing at number of cmds}
- cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(howManyCmds));
-
- {cruisePtr is now at the first sound command}
- REPEAT {cruise all commands and find a soundCmd or bufferCmd}
- CASE SndCmdPtr(cruisePtr)^.cmd OF
-
- (soundCmd + dataOffsetFlag), (bufferCmd + dataOffsetFlag):
- BEGIN
- dataType:= sampledSynth;
- GetSndDataOffset:= SndCmdPtr(cruisePtr)^.param2;
- howManyCmds:= 0; {done, get out of loop}
- END;
-
- (waveTableCmd + dataOffsetFlag):
- BEGIN
- dataType:= waveTableSynth;
- waveLength:= SndCmdPtr(cruisePtr)^.param1;
- GetSndDataOffset:= SndCmdPtr(cruisePtr)^.param2;
- howManyCmds:= 0; {done, get out of loop}
- END;
-
- OTHERWISE {catch any other type of cmd}
- BEGIN
- cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(sndCommand));
- howManyCmds:= howManyCmds - 1;
- END;
-
- END;
- UNTIL howManyCmds < 1; {done with all the commands}
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SupportedSH(sndPtr: SoundHeaderPtr): BOOLEAN;
-
- {VERSION 1.1: Check the given sound header as being supported by the
- running Sound Manager. The encode fields of the header are tested.
- The Standard encode always works. The Compressed sound will only work if
- MACE is present. A MACE sound can also be a stereo sound, which will only
- work on stereo hardware. The Expanded sound is for a stereo sound and/or
- 16bit samples and is only supported by the new Sound Manager. So far,
- only 8bit samples are supported. If the sound is a stereo sound, then
- it requires stereo hardware.}
-
- BEGIN
- CASE INTEGER(sndPtr^.encode) OF
-
- stdSH:
- SupportedSH:= TRUE;
-
- cmpSH: BEGIN
- IF gHasMACE THEN BEGIN
- SupportedSH:= (CmpSoundHeaderPtr(sndPtr)^.numChannels = 1)
- | ((CmpSoundHeaderPtr(sndPtr)^.numChannels > 1)
- & gHasStereo);
- END ELSE
- SupportedSH:= FALSE;
- END;
-
- extSH: BEGIN
- IF gNewSndMgr THEN BEGIN
- IF ExtSoundHeaderPtr(sndPtr)^.sampleSize <> 8 THEN
- SupportedSH:= FALSE
- ELSE
- SupportedSH:= (CmpSoundHeaderPtr(sndPtr)^.numChannels = 1)
- | ((CmpSoundHeaderPtr(sndPtr)^.numChannels > 1)
- & gHasStereo);
- END ELSE
- SupportedSH:= FALSE;
- END;
-
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION InstallSampleSnd(myChan: MyChanPtr; sndHandle: Handle): OSErr;
-
- {Given a channel and sampled sound resource, this routine will install the
- sound into the channel for use as an instrument. This allows an
- application to send freqDurationCmds to the channel and play a melody.
- If I sent a bufferCmd instead of the soundCmd, the Sound Manager would play
- the sampled sound. This is basically what _SndPlay would do with a format 2
- snd. I insure that I am using only the proper buffer format having the
- standard encode option. If I were to support compressed sounds, I would
- have to call the MACE synthesizers to expand the buffer before I can use
- it as an instrument. If I don’t get a sampled sound of standard encoding
- I’ll return a bad format error. I use _SndDoImmediate to get the sound
- installed because I don’t want this command to be queued.
-
- VERSION 1.1: Check for a supported sound header.}
-
- VAR
- theCmd: sndCommand;
- dataPtr: SoundHeaderPtr;
- dataOffset: LONGINT;
- sndDataType,
- ignore: INTEGER;
- theErr: OSErr;
-
- BEGIN
- theErr:= HoldSnd(sndHandle);
- IF theErr = noErr THEN BEGIN
- dataOffset:= GetSndDataOffset(sndHandle, sndDataType, ignore);
- IF sndDataType = sampledSynth THEN BEGIN
- dataPtr:= SoundHeaderPtr(ORD4(sndHandle^) + dataOffset);
- IF stdSH = INTEGER(dataPtr^.encode) THEN BEGIN
- WITH theCmd DO BEGIN
- cmd:= soundCmd;
- param1:= 0;
- param2:= ORD4(dataPtr);
- END;
- myChan^.dataHandle:= sndHandle;
- theErr:= SndDoImmediate(SndChannelPtr(myChan), theCmd);
- END ELSE
- theErr:= badFormat; {return a bad format error}
- END ELSE
- theErr:= badFormat; {return a bad format error}
- IF theErr <> noErr THEN BEGIN
- HUnlock(sndHandle); {and free up the resource}
- HPurge(sndHandle);
- END;
- END;
- InstallSampleSnd:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION GetSampleChan(VAR sampleChan: SndChannelPtr; init: LONGINT;
- sndInstrument: Handle): OSErr;
-
- {This routine will create a sampled sound channel using the INIT option
- given. Typically this will be 0. In any case with System 6.0x this
- option is ignored by the sampled sound synthesizer. The given sound
- resource will be installed into the channel for use as an instrument.
-
- WARNING: If the application does not want an instrument sound, then the
- sndInstrument handle MUST be passed in as NIL.
-
- BUG NOTE: The sampled sound synthesizer in System 6.0x does not check for
- a Memory Manager error when allocating its internal buffer. There is a
- call to NewPtr(1316) and if a NIL is returned, the Sound Manager will
- write randomly to low memory. This can occur when calling _SysBeep under
- low memory conditions. Also, this pointer is allocated into the
- application’s heap instead of the system’s.}
-
- VAR
- theErr: OSErr;
-
- BEGIN
- FreeAllChans;
- theErr:= SndNewChannel(SndChannelPtr(gChan1), sampledSynth,
- init, @DoCallBack);
- IF theErr = noErr THEN BEGIN
- gChan1^.theChan.userInfo:= sampledSynth;
- theErr:= ChanAvailable(SndChannelPtr(gChan1));
- IF (theErr = noErr) & (sndInstrument <> NIL) THEN
- theErr:= InstallSampleSnd(gChan1, sndInstrument);
- END;
- IF theErr <> noErr THEN
- FreeAllChans
- ELSE
- gChanOpen:= TRUE; {got an open channel}
- sampleChan:= SndChannelPtr(gChan1);
- GetSampleChan:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION InstallWave(waveChan: SndChannelPtr; aWavePtr: Ptr;
- waveLength: INTEGER): OSErr;
-
- {Given a channel and pointer to a wave table, this will install the wave
- for use as an instrument into the channel. If I find the application
- giving me a NIL pointer, I’ll return an error. I use _SndDoImmediate
- to get the sound installed because I don’t want this to be queued.}
-
- VAR
- theCmd: SndCommand;
-
- BEGIN
- IF aWavePtr <> NIL THEN BEGIN
- WITH theCmd DO BEGIN
- cmd:= waveTableCmd;
- param1:= waveLength;
- param2:= ORD4(aWavePtr);
- END;
- InstallWave:= SndDoImmediate(waveChan, theCmd);
- END ELSE
- InstallWave:= memPCErr; {Pointer Check failed}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION GetWaveChans(VAR waveChan1, waveChan2,
- waveChan3, waveChan4: SndChannelPtr): OSErr;
-
- {This will return four wave table channels with their waves installed.
- When I create a channel I will set the userInfo field marking it with the
- 'snth' associated to it. This must be done before calling ChanAvailable.
- Otherwise that test will fail. If I cannot obtain all four wave channels
- I will dispose of the ones I did get before returning the error. This
- routine expects to find four wave table pointers, or it will fail.}
-
- VAR
- theErr: OSErr;
-
- PROCEDURE NewWaveChan(VAR myChan: MyChanPtr; init: INTEGER);
- BEGIN
- theErr:= SndNewChannel(SndChannelPtr(myChan), waveTableSynth,
- init, @DoCallBack);
- IF theErr = noErr THEN BEGIN
- myChan^.theChan.userInfo:= waveTableSynth;
- theErr:= ChanAvailable(SndChannelPtr(myChan));
- END;
- END;
-
- BEGIN
- FreeAllChans;
- NewWaveChan(gChan1, initChan0);
- IF theErr = noErr THEN BEGIN
- NewWaveChan(gChan2, initChan1);
- IF theErr = noErr THEN BEGIN
- NewWaveChan(gChan3, initChan2);
- IF theErr = noErr THEN BEGIN
- NewWaveChan(gChan4, initChan3);
- END;
- END;
- END;
- IF theErr <> noErr THEN
- FreeAllChans {we didn’t make it}
- ELSE BEGIN
- waveChan1:= SndChannelPtr(gChan1);
- waveChan2:= SndChannelPtr(gChan2);
- waveChan3:= SndChannelPtr(gChan3);
- waveChan4:= SndChannelPtr(gChan4);
- gChanOpen:= TRUE; {now we’re making noise}
- END;
- GetWaveChans:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION GetSquareChan(VAR squareChan: SndChannelPtr; timbre: INTEGER): OSErr;
-
- {This will create a channel for the square wave synthesizer. When I create a
- channel I will set the userInfo field marking it with the 'snth'
- associated to it. This must be done before calling ChanAvailable.
- Otherwise that test will fail. There are no INIT options used by this
- synthesizer, but I will set the timbre to adjust the tone quality.}
-
- VAR
- theErr: OSErr;
-
- BEGIN
- FreeAllChans;
- theErr:= SndNewChannel(SndChannelPtr(gChan1), squareWaveSynth,
- kInitNone, @DoCallBack);
- IF theErr = noErr THEN BEGIN
- gChan1^.theChan.userInfo:= squareWaveSynth;
- theErr:= ChanAvailable(SndChannelPtr(gChan1));
- IF theErr = noErr THEN
- theErr:= SetSquareTimbre(SndChannelPtr(gChan1), timbre, NOT kWait);
- END;
- IF theErr <> noErr THEN
- FreeAllChans
- ELSE
- gChanOpen:= TRUE; {now we’re making noise}
- squareChan:= SndChannelPtr(gChan1);
- GetSquareChan:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION GetNoSynthChan(VAR chan: SndChannelPtr): OSErr;
-
- {This is the routine to create a channel that isn’t associated with any
- synthesizer. Why? Because if you wanted to use _SndPlay asynchronously
- you need to get such a channel. When I create a channel I will set the
- userInfo field marking it with the 'snth' associated to it. This must be
- done before calling ChanAvailable.
-
- BUG NOTE: Do not use a channel already associated to a snth with
- _SndPlay. This causes the Sound Manager to install a second copy of the
- same snth.}
-
- VAR
- theErr: OSErr;
-
- BEGIN
- FreeAllChans;
- theErr:= SndNewChannel(SndChannelPtr(gChan1), kNoSynth,
- kInitNone, @DoCallBack);
- IF theErr = noErr THEN BEGIN
- gChan1^.theChan.userInfo:= kNoSynth;
- theErr:= ChanAvailable(SndChannelPtr(gChan1));
- END;
- IF theErr <> noErr THEN
- FreeAllChans
- ELSE
- gChanOpen:= TRUE; {now we’re making noise}
- chan:= SndChannelPtr(gChan1);
- GetNoSynthChan:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION PlaySong(chan: SndChannelPtr; sndSong: Handle): OSErr;
-
- {This routine will use the given channel and snd resource with _SndPlay.
- This is used to play a sound, which is a series of sound commands commonly
- referred to as a sequence. First thing I do is make sure the song fits in
- memory. _SndPlay will lock this resource in memory and then pump the snd
- for all of its worth. I am calling it asynchronously, and if I was using
- a snd that contained sound data I wouldn’t mark the snd as being
- purgeable. But in this case, _SndPlay will be done with the snd as soon
- as it returns because it copied all of the commands into the channel.
- (There’s no data associated with a sequence, just commands.) _SndPlay
- will not return until it has done so. After _SndPlay I need to work
- around a bug in the freqDurationCmd. The last thing to do is to send a
- callBackCmd to signal me that the channel has completed. If any Sound
- Manager errors are encountered, I return them to the application. If the
- application passed me a NIL snd handle, I’ll return an error.
-
- WARNING: Make sure you are using a snd that only has frequency type commands
- in it and not something such as a bufferCmd.
-
- BUG NOTE: There is problem when the final sound command is a freqDurationCmd.
- The sound will continue to be heard, looping forever, until a quietCmd is sent
- or the channel is disposed of. To prevent unwanted looping, I send a
- quietCmd after all frequency commands. Also read a related bug note when
- disposing of channels in the routine FreeChan.}
-
- VAR
- theErr: OSErr;
-
- BEGIN
- theErr:= SndDataAvailable(sndSong); {get the data loaded}
- IF theErr = noErr THEN BEGIN
- theErr:= SndPlay(chan, SndListHandle(sndSong), kSMAsynch); {pump the sound}
- HUnlock(sndSong);
- HPurge(sndSong);
- IF theErr = noErr THEN BEGIN
- theErr:= SendQuiet(chan, kWait); {work around bug}
- IF theErr = noErr THEN
- theErr:= SoundComplete(chan);
- END;
- END ELSE
- PlaySong:= nilHandleErr; {snd data was not available}
- IF theErr <> noErr THEN
- FreeAllChans;
- PlaySong:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION ReleaseSynch(chan: SndChannelPtr): OSErr;
-
- {This is used to send a syncCmd to a channel and causes the other channels
- that are being held by a synchCmd to be released. Of course, this assumes
- the application has already called SynchChans. _SndDoImmediate is used
- to get the command directly to the synthesizer bypassing the queue.
-
- BUG NOTE: I’ve found that immediately clearing the channels and starting
- new ones may cause the channels to startup playing out of synch? This
- happens while disposing the wave channels and starting them immediately.}
-
- VAR
- theCmd: sndCommand;
-
- BEGIN
- WITH theCmd DO BEGIN
- cmd:= syncCmd;
- param1:= 1;
- param2:= kSyncID;
- END;
- ReleaseSynch:= SndDoImmediate(chan, theCmd);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SynchChans(chan1, chan2, chan3, chan4: SndChannelPtr): OSErr;
-
- {This is used to synchronize four wave table channels. By first sending
- the synchCmd, I can send a sequence of other commands to the channel and
- not have the channel attempt to start processing any of them. That is until
- another synchCmd is sent causing all of the previous synchCmd’s counter
- to be decremented. After getting all the channels in synch and sending
- the sequence of further commands, then use the ReleaseSynch routine to
- start all of the channels processing their respective queues.
- _SndDoImmediate is used to get the command directly to the synthesizer
- bypassing the queue.}
-
- VAR
- theCmd: SndCommand;
- theErr: OSErr;
-
- PROCEDURE Synch1Chan(chan: SndChannelPtr; count: INTEGER);
- BEGIN
- WITH theCmd DO BEGIN
- cmd:= syncCmd;
- param1:= count;
- param2:= kSyncID;
- END;
- theErr:= SndDoImmediate(chan, theCmd);
- END;
-
- BEGIN
- Synch1Chan(chan4, 5);
- IF theErr = noErr THEN BEGIN
- Synch1Chan(chan3, 4);
- IF theErr = noErr THEN BEGIN
- Synch1Chan(chan2, 3);
- IF theErr = noErr THEN BEGIN
- Synch1Chan(chan1, 2);
- END;
- END;
- END;
- SynchChans:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION Play4Waves(waveChan1, waveChan2, waveChan3, waveChan4: SndChannelPtr;
- song1, song2, song3, song4: Handle): OSErr;
-
- {In order to synchronize channels, the synchCmd is needed. Once all of the
- song has been sent into each channel, a final synchCmd is issued to
- release them. Don’t send more commands into a channel that it can hold at
- one time while the channel is in synch mode.}
-
- VAR
- theErr: OSErr;
-
- BEGIN
- theErr:= SynchChans(waveChan1, waveChan2, waveChan3, waveChan4);
- IF theErr = noErr THEN BEGIN
- theErr:= PlaySong(waveChan1, song1);
- IF theErr = noErr THEN BEGIN
- theErr:= PlaySong(waveChan2, song2);
- IF theErr = noErr THEN BEGIN
- theErr:= PlaySong(waveChan3, song3);
- IF theErr = noErr THEN BEGIN
- theErr:= PlaySong(waveChan4, song4);
- IF theErr = noErr THEN
- theErr:= ReleaseSynch(waveChan1);
- END;
- END;
- END;
- END;
- Play4Waves:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION HyperSndPlay(sndHandle: Handle): OSErr;
-
- {WARNING: IT IS RECOMMENDED THAT YOU DO NOT USE THIS CODE. I’ve provided
- this routine because people have asked me how HyperCard performs its PLAY
- command and why their HyperCard sounds do not sound right using the
- _SndPlay routine. The correct answer is that _SndPlay plays the sound
- correctly. HyperCard is attempting to change the frequency by adjusting
- the sample rate. This is NOT the correct approach. Define the sound
- buffer as it should be played. _SndPlay plays the sound as it is defined.
- If the result from _SndPlay is not what you want, then it is the sample
- that is incorrect and should be edited. The sample rate is the rate at
- which the sound was recorded. If you didn’t record it, then how do you
- know what’s the correct rate? Set the baseFrequency to the frequency that
- was recorded. If you recorded middle C at 22k, then the rate is 22k and the
- baseFrequency is middle C. HyperCard is incorrect in using the sample rate
- as the frequency of the sound. Furthermore, using this technique of
- calculating a new sample rate can introduce errors. The resulting sample
- rate will not be the proper pitch. Also, the sample rate for high pitches
- will be very inaccurate and impossible for the Mac to reproduce. Such a
- problem can happen if the given sample rate was 22k and is to be played
- back at three octaves higher. Even 44k samples transposed up a half
- octave will fail. Using the soundCmd and freqDurationCmd will not have
- this problem.
-
- Given a sound resource, this routine will play it in the manner that
- HyperCard does. HyperCard assumes that a sound is to be played at middle
- C when the user does not specify a frequency value in the PLAY command. I
- don’t know why. (What’s middle C when I want to hear speech or the sound
- of crickets?) At any rate (pun intended), I get a sampled sound channel.
- I find the sound data offset in the resource, which has to be locked down
- at this time. Once I have the sound data, I get its original sample rate.
- I have to calculate what a new sample rate would be based on its baseFrequency.
- The baseFrequency is the frequency at which the sound was recorded. I’m
- not sure what this means to crickets, but if this is set to middle C then
- HyperCard doesn’t attempt to modify the sample rate. (If you’re wondering
- how the math works in this routine, buy a book on music theory. I’m here to
- provide Mac support.) Once I’ve adjusted the sample rate, I use the
- bufferCmd to play it. Then I restore the sound resource to its original
- state. If I didn’t do this it would be possible that the resource was
- still in memory the next time I use it having the adjusted sample rate.
- This would cause me to incorrectly adjust it again. Unlike HyperCard, I
- can do this for both a format 1 and 2.
-
- BUG NOTE: Do not call SANE of the FPU while the Sound Manager is running.
- Refer to Tech Note #235. This problem was fixed in the new Sound Manager.
-
- VERSION 1.1: Replaced the previous test of the sound header's encode
- value. The previous version was incorrect. The bufferCmd will automatically
- de-code a MACE compressed sound if MACE is available. Otherwise, the sound
- will not be able to be used. So, a new routine is being used to check for
- supported sound headers. The conflict with the Sound Manager and SANE was
- resolved in the new Sound Manager. It no longers uses extended numbers, and
- instead uses fixed math.}
-
- VAR
- theCmd: SndCommand;
- newRate: Extended;
- oldRate: Fixed;
- dataPtr: SoundHeaderPtr;
- dataOffset: LONGINT;
- sndDataType,
- power, ignore: INTEGER;
- theErr: OSErr;
- pextended: Extended;
- index: INTEGER;
-
- BEGIN
- theErr:= HoldSnd(sndHandle);
- IF theErr = noErr THEN BEGIN
- theErr:= GetSampleChan(SndChannelPtr(gChan1), kInitNone, NIL);
- gChan1^.dataHandle:= sndHandle; {so FreeAllChans can dispose of data}
- IF theErr = noErr THEN BEGIN
-
- dataOffset:= GetSndDataOffset(sndHandle, sndDataType, ignore);
- IF sndDataType = sampledSynth THEN BEGIN
- dataPtr:= SoundHeaderPtr(ORD4(sndHandle^) + dataOffset);
- IF SupportedSH(dataPtr) THEN BEGIN
- oldRate:= dataPtr^.sampleRate; {save original sample rate}
- IF NOT (INTEGER(dataPtr^.baseFrequency) = kMiddleC) THEN BEGIN
- IF dataPtr^.sampleRate < 0 THEN {large positive number}
- newRate:= (Fix2X(BAnd(dataPtr^.sampleRate, maxLongInt))
- + maxLongInt + 1) {twos comp. is off by one}
- ELSE
- newRate:= Fix2X(dataPtr^.sampleRate);
- newRate:= Fix2X(dataPtr^.sampleRate);
- power:= kMiddleC - INTEGER(dataPtr^.baseFrequency);
- pextended := 1.0;
- FOR index:= 1 TO power DO BEGIN
- pextended := pextended * twelfthRootTwo
- END;
- dataPtr^.sampleRate:= X2Fix(pextended * newRate);
- END;
- WITH theCmd DO BEGIN
- cmd:= bufferCmd;
- param1:= 0;
- param2:= ORD4(dataPtr);
- END;
- theErr:= SndDoImmediate(SndChannelPtr(gChan1), theCmd);
- IF theErr = noErr THEN
- theErr:= SoundComplete(SndChannelPtr(gChan1));
- dataPtr^.sampleRate:= oldRate; {restore original sample rate}
- END ELSE
- theErr:= badFormat; {not SupportedSH}
- END ELSE
- theErr:= badFormat; {sndDataType not sampledSynth}
- END;
- END;
- IF theErr <> noErr THEN
- FreeAllChans;
- HyperSndPlay:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION AsynchSndPlay(sndHandle: Handle): OSErr;
-
- {Given a sound resource, this routine will call _SndPlay. The snd must
- be either a format 2 or format 1 that contains snth information.
- Using _SndPlay asynchronously requires us to lock the snd prior to
- calling the trap. The reason being is _SndPlay remembers the state of the
- lock bit using _HGetState and _HSetState. If the snd is unlocked when
- it’s passed to _SndPlay, it will be unlocked again when _SndPlay exits.
- This would be bad when using the sound asynchronously. If the sound being
- passed in happens to be a compressed sound created with MACE, it will “do
- the right thing.” If MACE isn’t around the Sound Manager will pretend to
- play a sound but nothing will be heard.
-
- BUG NOTE: The sampled sound synthesizer in System 6.0x does not check for
- a Memory Manager error when allocating its internal buffer. There is a
- call to NewPtr(1316) and if a NIL is return, the Sound Manager will write
- randomly to memory. Also, the pointer is allocated into the application’s
- heap instead of the system’s.
-
- BUG NOTE: _SndPlay when using System 6.0.4 and a sampled sound will send
- a bogus callBackCmd into the channel. This will cause the user’s call
- back procedure to be called as soon as the sound has completed. Refer
- to the DoCallBack routine for details.
-
- VERSION 1.1: Add the check for the sound header being supported and
- replaced the check of the snth information. No synth information in the
- snd is valid and would mean to play the snd using the squareWaveSynth.}
-
- VAR
- dataPtr: SoundHeaderPtr;
- dataOffset: LONGINT;
- sndDataType,
- ignore: INTEGER;
- theErr: OSErr;
-
- BEGIN
- theErr:= HoldSnd(sndHandle); {hold on to the sound}
- IF theErr = noErr THEN BEGIN
- theErr:= GetNoSynthChan(SndChannelPtr(gChan1));
- gChan1^.dataHandle:= sndHandle; {so FreeAllChans can dispose of data}
- IF theErr = noErr THEN BEGIN
- gChan1^.theChan.userInfo:= GetSynthInfo(sndHandle).synthID;
- dataOffset:= GetSndDataOffset(sndHandle, sndDataType, ignore);
- IF sndDataType = sampledSynth THEN BEGIN
- dataPtr:= SoundHeaderPtr(ORD4(sndHandle^) + dataOffset);
- IF NOT SupportedSH(dataPtr) THEN
- theErr:= badFormat;
- END;
- IF theErr = noErr THEN BEGIN
- theErr:= SndPlay(SndChannelPtr(gChan1), SndListHandle(sndHandle), kSMAsynch);
- IF theErr = noErr THEN
- theErr:= SoundComplete(SndChannelPtr(gChan1));
- END;
- END;
- END;
- IF theErr <> noErr THEN
- FreeAllChans;
- AsynchSndPlay:= theErr;
- END;
-
-
-
- END. {UNIT}
-